package com.nulldev.util.uri.yt;

import java.util.LinkedList;
import java.util.regex.Pattern;

import com.nulldev.util.java.DataSet;
import com.nulldev.util.lang.json.Json;
import com.nulldev.util.lang.json.JsonArray;
import com.nulldev.util.math.MathUtil;
import com.nulldev.util.string.StringsUtil;
import com.nulldev.util.uri.Soup_imported.Jsoup;
import com.nulldev.util.uri.Soup_imported.nodes.Document;
import com.nulldev.util.uri.Soup_imported.nodes.Element;
import com.nulldev.util.uri.Soup_imported.select.Elements;
import com.nulldev.util.uri.yt.MetadataExtractor.EndVideoData;
import com.nulldev.util.uri.yt.MetadataExtractor.EndscreenVideo;

public class VideoMeta {

    private static final String IMAGE_BASE_URL = "http://i.ytimg.com/vi/";

    private String videoId;
    private String title;

    private String author;
    private String channelId;
    private String captions_url;

    private long videoLength;
    private long viewCount;

    private boolean isLiveStream;
    private boolean fastMode;
    
    private double avgRating = 0.0;
    private EndVideoData end;
    private LinkedList<EndscreenVideo> ends;
    private String[] tags;
    private String description = null;
    private boolean verify_controversy;
    
    private JsonArray captions = Json.array();

    protected VideoMeta(String videoId, String title, String author, String channelId,
    		long videoLength, long viewCount, boolean isLiveStream, EndVideoData end, String[] tags, DataSet<LinkedList<EndscreenVideo>, Long> ends) {
        this.videoId = videoId;
        this.title = title;
        this.author = author;
        this.channelId = channelId;
        this.videoLength = videoLength;
        this.viewCount = viewCount;
        this.isLiveStream = isLiveStream;
        this.end = end;
        this.tags = tags;
        this.ends = ends.getA();
        this.subs = ends.getB();
        this.lockedPlayback = false;
    }
    
    public boolean isControversial() {
    	return this.verify_controversy;
    }
    
    public void setControverisal(final boolean val) {
    	this.verify_controversy = val;
    }
    
    protected VideoMeta(String videoId, String title, String author, String channelId,
    		long videoLength, long viewCount, boolean isLiveStream, double avgRating,
    		EndVideoData end, String[] tags, DataSet<LinkedList<EndscreenVideo>, Long> ends, 
    		final boolean fastMode, final String captions_url,
    		final JsonArray captions) {
    	this(videoId, title, author, channelId, videoLength, viewCount, isLiveStream, avgRating, end, tags, ends, false, 
    			fastMode, captions_url, captions);
    }
    
    protected VideoMeta(String videoId, String title, String author, String channelId,
    		long videoLength, long viewCount, boolean isLiveStream, double avgRating,
    		EndVideoData end, String[] tags, DataSet<LinkedList<EndscreenVideo>, Long> ends, 
    		final boolean lockedVideoToNative, final boolean fastMode , final String captions_url,
    		final JsonArray captions) {
        this.videoId = videoId;
        this.title = title;
        this.author = author;
        this.channelId = channelId;
        this.videoLength = videoLength;
        this.viewCount = viewCount;
        this.isLiveStream = isLiveStream;
        this.avgRating = avgRating;
        this.end = end;
        this.tags = tags;
        this.ends = ends.getA();
        this.subs = ends.getB();
        this.lockedPlayback = lockedVideoToNative;
        this.fastMode = fastMode;
        this.captions_url = captions_url;
        this.captions = captions;
    }


    // 120 x 90
    /**
     * 120x90
     */
    public String getThumbUrl() {
        return IMAGE_BASE_URL + videoId + "/default.jpg";
    }

    // 320 x 180
    /**
     * 320x180
     * 
     */
    public String getMqImageUrl() {
        return IMAGE_BASE_URL + videoId + "/mqdefault.jpg";
    }

    // 480 x 360
    /**
     * 480x360
     * @return
     */
    public String getHqImageUrl() {
        return IMAGE_BASE_URL + videoId + "/hqdefault.jpg";
    }

    // 640 x 480
    /**
     * 640x480
     * @return
     */
    public String getSdImageUrl() {
        return IMAGE_BASE_URL + videoId + "/sddefault.jpg";
    }

    // Max Res
    /**
     * Max Res
     * @return
     */
    public String getMaxResImageUrl() {
        return IMAGE_BASE_URL + videoId + "/maxresdefault.jpg";
    }

    public String getVideoId() {
        return videoId;
    }

    public String getTitle() {
        return title;
    }

    public String getAuthor() {
        return author;
    }

    public String getChannelId() {
        return channelId;
    }

    public boolean isLiveStream() {
        return isLiveStream;
    }
    
    public String[] getTags() {
    	return this.tags;
    }

    /**
     * The video length in seconds.
     */
    public long getVideoLength() {
        return videoLength;
    }

    public long getViewCount() {
        return viewCount;
    }
    
    public double getRating() {
    	return this.avgRating;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        VideoMeta videoMeta = (VideoMeta) o;

        if (videoLength != videoMeta.videoLength) return false;
        if (viewCount != videoMeta.viewCount) return false;
        if (isLiveStream != videoMeta.isLiveStream) return false;
        if (videoId != null ? !videoId.equals(videoMeta.videoId) : videoMeta.videoId != null)
            return false;
        if (title != null ? !title.equals(videoMeta.title) : videoMeta.title != null) return false;
        if (author != null ? !author.equals(videoMeta.author) : videoMeta.author != null)
            return false;
        return channelId != null ? channelId.equals(videoMeta.channelId) : videoMeta.channelId == null;

    }

    @Override
    public int hashCode() {
        int result = videoId != null ? videoId.hashCode() : 0;
        result = 31 * result + (title != null ? title.hashCode() : 0);
        result = 31 * result + (author != null ? author.hashCode() : 0);
        result = 31 * result + (channelId != null ? channelId.hashCode() : 0);
        result = 31 * result + (int) (videoLength ^ (videoLength >>> 32));
        result = 31 * result + (int) (viewCount ^ (viewCount >>> 32));
        result = 31 * result + (isLiveStream ? 1 : 0);
        result = 31 * result + (int) ((int) (avgRating) ^ ((int) (avgRating) >>> 32));
        return result;
    }

    @Override
    public String toString() {
        return "VideoMeta{" +
                "videoId='" + videoId + '\'' +
                ", title='" + title + '\'' +
                ", author='" + author + '\'' +
                ", channelId='" + channelId + '\'' +
                ", videoLength=" + videoLength +
                ", viewCount=" + viewCount +
                ", isLiveStream=" + isLiveStream +
                ", avgRating=" + avgRating +
                '}';
    }
    
    public EndVideoData getEndData() {
    	return this.end;
    }

    private long subs = -1L, likes = -1, dislikes = -1;
    private boolean familyFriendly, unlistedVideo;
    
	public long getSubCount() {
		if (this.end.getSubCount() != -1) {
			return this.end.getSubCount();
		}
		return this.subs;
	}

	public LinkedList<EndscreenVideo> getEndScreen() {
		return this.ends;
	}
	
	private String pageMap, embedMap;
	
	void __applyMaps(final String pageMap, final String embedMap) {
		this.pageMap = pageMap;
		this.embedMap = embedMap;
	}
	
	public String getDescription() {
		if (this.description == null) {
			try {
				//final String page = URIUtil.downloadPage(
				//		URIUtil.safeCreate("https://www.youtube.com/watch?v=" + this.videoId + (this.verify_controversy ? 
				//				"&bpctr=" + System.currentTimeMillis() : "")));
				if (this.pageMap == null) {
					//?????????????????????????????????? it is because of us not talking to YouTube?
					return "No description found, are we live?";
				}
				final Document d = Jsoup.parse(this.pageMap); //page);
				//final Elements eles = d.getElementsByTag("meta");
				String desc = "No description provided.";
				try {
					desc = d.getElementById("eow-description").html();
					//"No description provided.";
				}catch (Exception e) {
					e.printStackTrace();
				}
				//for (final Element ele : eles) {
				//	final String content = ele.attr("content");
				//	final String name = ele.attr("name");
				//	if (name.equals("description") && content != null) {
				//		desc = content;
				//	}
				//}
				final Elements subs0 = d.getElementsByClass("yt-subscription-button-subscriber-count-branded-horizontal yt-subscriber-count");
				Element subs1 = subs0.first();
				if (subs1 != null) {
					final String subsText = subs1.text();
					this.subs = MathUtil.fromFormat(subsText);
				}else {
				}
				
				//this.subs = ///
				if (desc == null) {
					this.description = "No description provided. [Video is in censorship mode?]";
				}else {
					this.description = desc;
				}
				this.familyFriendly = this.pageMap.contains("<meta itemprop=\"isFamilyFriendly\" content=\"True\">");
				this.unlistedVideo = this.pageMap.contains("<meta itemprop=\"unlisted\" content=\"True\">");
				final Elements likes0 = d.getElementsByClass("yt-uix-button yt-uix-button-size-default yt-uix-button-opacity yt-uix-button-has-icon no-icon-markup like-button-renderer-like-button like-button-renderer-like-button-unclicked yt-uix-clickcard-target   yt-uix-tooltip");
				if (likes0.size() == 1) {
					String toParse = StringsUtil.substringBetween(likes0.get(0).text(), "<span class=\"yt-uix-button-content\">", "</span>");
					if (toParse.contains(" ")) {
						toParse = toParse.replaceAll(Pattern.quote(" "), "");
					}
					final long parsed = MathUtil.fromFormat(toParse);
					this.likes = parsed;
				}
				final Elements dislikes0 = d.getElementsByClass("yt-uix-button yt-uix-button-size-default yt-uix-button-opacity yt-uix-button-has-icon no-icon-markup like-button-renderer-dislike-button like-button-renderer-dislike-button-unclicked yt-uix-clickcard-target   yt-uix-tooltip");
				if (dislikes0.size() == 1) {
					String toParse = StringsUtil.substringBetween(dislikes0.get(0).text(), "<span class=\"yt-uix-button-content\">", "</span>");
					if (toParse.contains(" ")) {
						toParse = toParse.replaceAll(Pattern.quote(" "), "");
					}
					final long parsed = MathUtil.fromFormat(toParse);
					this.dislikes = parsed;
				}
				final Element trending = d.getElementsByClass(" yt-uix-sessionlink      spf-link ").get(0);
				if (trending != null && trending.attr("href").equals("/feed/trending")) {
					VideoMeta.this.trending = trending.text();
				}
			}catch (Exception e) {
				e.printStackTrace();
				this.description = "No description provided.";
			}
		}
		return this.description;
	}
	
	public long getLikes() {
		return this.likes;
	}
	
	public long getDislikes() {
		return this.dislikes;
	}
	
	public boolean isFamilyFriendly() {
		return this.familyFriendly;
	}
	
	public boolean isUnlisted() {
		return this.unlistedVideo;
	}
	
	private String trending = "null";

	public String getTrending() {
		return this.trending;
	}
	
	private boolean lockedPlayback;

	public boolean lockedPlayback() {
		return this.lockedPlayback;
	}
	
	public boolean useFastMode() {
		return this.fastMode;
	}
	
	public String getCaptionsURL() {
		return this.captions_url;
	}

	private String playlistNextURL = null;
	public void setPlaylistNextURL(final String playlistNextURL) {
		this.playlistNextURL = playlistNextURL;
	}
	
	public boolean hasPlaylist() {
		return this.playlistNextURL != null;
	}
	
	public String getPlaylistNextURL() {
		return this.playlistNextURL;
	}
	
	private String errorString = null;
	private String advErrorString = null;
	
	public boolean hasErrorString() {
		return this.errorString != null;
	}
	
	public boolean hasAdvancedErrorString() {
		return this.advErrorString != null;
	}
	
	public String getAdvancedErrorString() {
		return this.advErrorString;
	}
	
	public String getErrorString() {
		return this.errorString;
	}
	
	public void setErrorString(final String string) {
		this.errorString = string;
	}
	
	public void setAdvancedErrorString(final String string) {
		this.advErrorString = string;
	}
	
	public boolean hasCaptionsInArray() {
		return this.captions != null && this.captions.size() > 0;
	}
	
	public JsonArray captionArray() {
		return this.captions;
	}

	public String getPageMap() {
		return this.pageMap;
	}
	
	public String getEmbedMap() {
		return this.embedMap;
	}

	private boolean is360;
	
	void set360(final boolean is360Video) {
		this.is360 = is360Video;
	}
	
	public boolean is360Video() {
		return this.is360;
	}
}
